package resp

import (
	"bytes"
	"encoding/json"
	"errors"
	"html/template"
	"io/fs"
	"log"
	"net/http"
)

type Marshaller func(data interface{}) []byte

// Response define standard response function
type Response interface {
	//build response
	Status(httpCode int) Response
	Data(data interface{}) Response
	Marshaller(marshaller Marshaller) Response
	ContentType(cType string) Response

	GetStatus() int
	GetData() interface{}
	GetContentType() string
	GetMarshaller() Marshaller

	// Write the response
	// when content-type is application/json, data will auto marshal to json
	// other time will convert to string
	Write(w http.ResponseWriter)

	//error
	Error() string
}

type r struct {
	status      int
	data        interface{}
	contentType string
	marshaller  Marshaller
}

func (r *r) Status(httpCode int) Response {
	r.status = httpCode
	return r
}

func (r *r) Data(data interface{}) Response {
	r.data = data
	return r
}

func (r *r) Marshaller(marshaller Marshaller) Response {
	r.marshaller = marshaller
	return r
}

func (r *r) ContentType(cType string) Response {
	r.contentType = cType
	return r
}

func (r *r) GetStatus() int {
	return r.status
}

func (r *r) GetData() interface{} {
	return r.data
}

func (r *r) GetMarshaller() Marshaller {
	if r.marshaller != nil {
		return r.marshaller
	}
	if marshaller, ok := autoMarshaller[r.GetContentType()]; ok {
		r.marshaller = marshaller
	} else {
		r.marshaller = RawMarshaller
	}
	return r.marshaller
}

func (r r) GetContentType() string {
	return r.contentType
}

func (r *r) write(w http.ResponseWriter, data []byte) {
	w.Header().Set(ContentTypeHeaderKey, ContentType(r.GetContentType()))
	w.WriteHeader(r.GetStatus())
	_, err := w.Write(data)
	if err != nil {
		log.Println(err.Error())
	}
}

func (r *r) Write(w http.ResponseWriter) {
	r.write(w, r.GetMarshaller()(r.GetData()))
}

func (r *r) Error() string {
	return string(r.GetMarshaller()(r.GetData()))
}

/*
Template
*/
type IModelAndView interface {
	SetModel(model interface{}) IModelAndView
	SetAttributes(key string, value interface{}) IModelAndView
	SetView(file string) IModelAndView
	SetFS(fs fs.FS, file string) IModelAndView
	Compile() (raw string, err error)
	Response() Response
	Write(w http.ResponseWriter)
}

type mode string

const (
	viewMode mode = "view"
	fsMode   mode = "fs"
)

type mv struct {
	view  string
	fs    fs.FS
	mode  mode
	model map[string]interface{}
}

func (r *mv) SetModel(model interface{}) IModelAndView {
	var modelMap map[string]interface{}
	modelBytes, _ := json.Marshal(model)
	json.Unmarshal(modelBytes, &modelMap)
	r.model = modelMap
	return r
}

func (r *mv) SetAttributes(key string, value interface{}) IModelAndView {
	if r.model == nil {
		r.model = make(map[string]interface{})
	}
	r.model[key] = value
	return r
}

func (r *mv) SetView(file string) IModelAndView {
	r.view = file
	r.mode = viewMode
	return r
}

func (r *mv) SetFS(fs fs.FS, file string) IModelAndView {
	r.mode = fsMode
	r.fs = fs
	r.view = file
	return r
}

func (r *mv) Compile() (raw string, err error) {
	var tpl *template.Template
	switch r.mode {
	case viewMode:
		tpl, err = template.ParseFiles(r.view)
		if err != nil {
			return
		}
		var buf bytes.Buffer
		err = tpl.Execute(&buf, r.model)
		if err != nil {
			return
		}
		raw = buf.String()
		return
	case fsMode:
		tpl, err = template.ParseFS(r.fs, r.view)
		if err != nil {
			return
		}
		var buf bytes.Buffer
		err = tpl.Execute(&buf, r.model)
		if err != nil {
			return
		}
		raw = buf.String()
		return
	default:
		return "", errors.New("view not set")
	}
}

func (r *mv) Response() Response {
	raw, err := r.Compile()
	if err != nil {
		return ErrInternalError(err)
	}
	return New(raw).ContentType(ContentHTMLHeaderValue)
}

func (r *mv) Write(w http.ResponseWriter) {
	raw, err := r.Compile()
	if err != nil {
		ErrInternalError(err).Write(w)
	}
	New(raw).ContentType(ContentHTMLHeaderValue).Write(w)
}
